import Dep from './observer/dep.js'
import { observe } from './observer/index.js'
import Watcher from './observer/watcher.js'

export function initState(vm) {
  const opts = vm.$options
  if (opts.props) {
    initProps(vm)
  }
  if (opts.methods) {
    initMethod(vm)
  }
  if (opts.data) {
    initData(vm)
  }
  if (opts.computed) {
    initComputed(vm)
  }
  if (opts.watch) {
    initWatch(vm)
  }
}

function initData(vm) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}

  for (let key of Object.keys(data)) {
    proxy(vm, `_data`, key)
  }

  observe(data)
}

function proxy(object, sourceKey, key) {
  Object.defineProperty(object, key, {
    get() {
      return object[sourceKey][key]
    },
    set(newValue) {
      object[sourceKey][key] = newValue
    },
  })
}

function initWatch(vm) {
  Vue.prototype.$watch = function (exprOrFn, cb, options) {
    const vm = this
    let watcher = new Watcher(vm, exprOrFn, cb, { ...options, user: true })
    if (options.immediate) {
      cb()
    }
  }
  let watch = vm.$options.watch
  for (let k of Object.keys(watch)) {
    const handler = watch[k]
    if (Array.isArray(handler)) {
      handler.forEach((handle) => {
        createWatcher(vm, k, handle)
      })
    } else {
      createWatcher(vm, k, handler)
    }
  }
}

function createWatcher(vm, key, handler, options = {}) {
  if (typeof handler === 'object') {
    options = handler
    handler = handler.handler
  }

  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(key, handler, options)
}

function initComputed(vm) {
  const computed = vm.$options.computed
  const watchers = (vm._computedWatchers = {})
  for (let k of Object.keys(computed)) {
    const userDef = computed[k]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    watchers[k] = new Watcher(vm, getter, () => {}, { lazy: true })
    defineComputed(vm, k, userDef)
  }
}

const sharedPropertyDefinition = {
  emumerable: true,
  configurable: true,
  get() {},
  set() {},
}

function defineComputed(target, key, userDef) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key)
  } else {
    sharedPropertyDefinition.get = createComputedGetter(key)
    sharedPropertyDefinition.set = userDef.set
  }

  Object.defineProperty(target, key, sharedPropertyDefinition)
}
// computed 核心
function createComputedGetter(key) {
  return function () {
    const watcher = this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
        if (Dep.target) {
          watcher.depend()
        }
      }
      return watcher.value
    }
  }
}
